﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;

using ICSharpCode.SharpZipLib.Zip;
using ICSharpCode.SharpZipLib.Zip.Compression;
using ICSharpCode.SharpZipLib.Zip.Compression.Streams;

namespace RIFT.Assets
{
    /// <summary>
    /// RIFT Package.
    /// </summary>
    public static class RiftPackage
    {
        /// <summary>
        /// Gets the files from a RIFT package.
        /// </summary>
        /// <param name="path">The package to be opened for reading.</param>
        /// <returns>A collection of files from the package.</returns>
        public static IEnumerable<RiftPackageFile> GetFiles(string path)
        {
            using (var stream = File.OpenRead(path))
            {
                foreach (var item in GetFilesFromStream(stream))
                {
                    yield return item;
                }
            }
        }

        /// <summary>
        /// Gets a single file from a RIFT package.
        /// </summary>
        /// <param name="path">The package to be opened for reading.</param>
        /// <param name="header">The <see cref="RiftPackageFileHeader"/> of the file to retrieve.</param>
        /// <returns>The <see cref="RiftPackageFile"/> that matches the header.</returns>
        public static RiftPackageFile GetFile(string path, RiftPackageFileHeader header)
        {
            using (var stream = File.OpenRead(path))
            {
                return GetFileFromStream(stream, header);
            }
        }

        /// <summary>
        /// Gets a single file from a RIFT package stream.
        /// </summary>
        /// <param name="stream">The stream.</param>
        /// <param name="header">The <see cref="RiftPackageFileHeader"/> of the file to retrieve.</param>
        /// <returns>The <see cref="RiftPackageFile"/> that matches the header.</returns>
        public static RiftPackageFile GetFileFromStream(Stream stream, RiftPackageFileHeader header)
        {
            using (var reader = new BinaryReader(stream))
            {
                return GetFileFromReader(reader, header);
            }
        }


        /// <summary>
        /// Gets the files from a RIFT package stream.
        /// </summary>
        /// <param name="stream">The stream.</param>
        /// <returns>A collection of files from the data stream.</returns>
        public static IEnumerable<RiftPackageFile> GetFilesFromStream(Stream stream)
        {
            using (var reader = new BinaryReader(stream))
            {
                // It's important that all headers are read out (by ToList()) before 
                // the files are read out, as jumping on the stream position
                // otherwise will give broken reads.
                var headers = GetHeadersFromReader(reader).ToList();

                foreach (var header in headers)
                {
                    yield return GetFileFromReader(reader, header);
                }
            }
        }

        /// <summary>
        /// Gets the headers from a RIFT package.
        /// </summary>
        /// <param name="path">The package to be opened for reading.</param>
        /// <returns>A collection of headers from the package.</returns>
        public static IEnumerable<RiftPackageFileHeader> GetHeaders(string path)
        {
            using (var stream = File.OpenRead(path))
            {
                foreach (var item in GetHeadersFromStream(stream))
                {
                    yield return item;
                }
            }
        }

        /// <summary>
        /// Gets the headers from a RIFT package stream.
        /// </summary>
        /// <param name="stream">The stream.</param>
        /// <returns>A collection of headers from the package.</returns>
        public static IEnumerable<RiftPackageFileHeader> GetHeadersFromStream(Stream stream)
        {
            using (var reader = new BinaryReader(stream))
            {
                foreach (var item in GetHeadersFromReader(reader))
                {
                    yield return item;
                }
            }
        }

        /// <summary>
        /// Gets a single file from a RIFT package stream reader.
        /// </summary>
        /// <param name="reader">The stream reader.</param>
        /// <param name="header">The <see cref="RiftPackageFileHeader"/> of the file to retrieve.</param>
        /// <returns>The <see cref="RiftPackageFile"/> that matches the header.</returns>
        private static RiftPackageFile GetFileFromReader(BinaryReader reader, RiftPackageFileHeader header)
        {
            reader.BaseStream.Position = header.Offset;

            var data = reader.ReadBytes(header.Size);

            if (header.IsCompressed)
            {
                data = RiftPackage.Decompress(data);
            }

            return new RiftPackageFile(data) 
            {
                Header = header
            };
        }

        /// <summary>
        /// Gets the headers from a RIFT package stream reader.
        /// </summary>
        /// <param name="reader">The stream reader.</param>
        /// <param name="path">The package to be opened for reading.</param>
        /// <returns>A collection of headers from the package.</returns>
        private static IEnumerable<RiftPackageFileHeader> GetHeadersFromReader(BinaryReader reader)
        {
            reader.ReadBytes(12);

            int count = reader.ReadInt32();
            var headers = new List<RiftPackageFileHeader>();

            for (int i = 0; i < (count / 60); i++)
            {
                var header = new RiftPackageFileHeader();

                header.UnknownA = new int[5];
                header.UnknownA[0]= reader.ReadInt32();
                header.UnknownA[1] = reader.ReadInt32();
                header.UnknownA[2] = reader.ReadInt32();
                header.UnknownA[3] = reader.ReadInt32();
                reader.ReadInt32();

                header.Size = reader.ReadInt32();
                int offset = reader.ReadInt32();
                header.UnknownB = reader.ReadInt32();
                header.Offset = reader.ReadInt32();
                header.UnknownC = reader.ReadBytes(24);

                if (header.Size != offset)
                {
                    header.IsCompressed = true;
                }

                yield return header;
            }
        }

        /// <summary>
        /// Decompresses the input using DEFLATE.
        /// </summary>
        /// <param name="input">DEFLATE compressed input.</param>
        /// <returns>Decompressed content as a byte array.</returns>
        public static byte[] Decompress(byte[] input)
        {
            var buffer = new byte[1];
            var output = new MemoryStream();

            using (var mStream = new MemoryStream(input))
            using (var iiStream = new InflaterInputStream(mStream))
            {
                int length = 0;

                while ((length = iiStream.Read(buffer, 0, buffer.Length)) > 0)
                {
                    output.Write(buffer, 0, buffer.Length);
                }
            }

            return output.ToArray();
        }
    }
}
